feat: create-agent-bundle scaffolding package (RFC #50 Phase 3) - #62
Conversation
`npm create agent-bundle` / `npx create-agent-bundle` scaffolds a ready-to-run plugin project from one of three checked-in templates — minimal (skills-only), mcp-server (conventional src/mcp/<id>.ts factory entry plus an artifact script), and cli-tool (src/cli.ts bin convention plus a src/index.ts library export). Prompt-driven (name, template, host targets) with full non-interactive flags (--template, --targets, --package-manager, --no-install, --framework-version); scaffolded projects pin agent-bundle to the pkg.pr.new preview of the same commit the scaffolder shipped from, and validate with zero diagnostics including the AB473x convention nudges. The packed e2e drives the installed scaffolder tarball and each scaffolded project's own check against the real agent-bundle tarball.
🦋 Changeset detectedLatest commit: 546a23e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc70d6708a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const cleaned = bare | ||
| .replace(/[^a-zA-Z0-9._-]+/gu, '-') | ||
| .replace(/^[^a-zA-Z0-9]+/u, '') | ||
| .replace(/[^a-zA-Z0-9]+$/u, ''); |
There was a problem hiding this comment.
Normalize names for selectable Cursor targets
When the project name contains uppercase letters or underscores and the user selects cursor or the unified plugin target, this sanitizer preserves those characters, while the Cursor adapter accepts only lowercase letters, digits, dots, and hyphens (packages/agent-bundle/src/adapters/cursor.ts:67-71). The newly scaffolded project's validate/check therefore fails immediately; normalize to a Cursor-compatible name or reject and re-prompt for these target selections.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 546a23e: the derived plugin name is now sanitized to the strictest host contract — Cursor's lowercase kebab-case (/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/, 64-char cap), which the unified plugin target enforces too and which remains a valid safe package-output name. Every target selection now validates regardless of the input casing; the unit test locks the mirrored pattern (My_App → my-app, @scope/My.Tool → my.tool, plus non-ASCII and over-length inputs).
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…ase kebab-case contract The derived plugin name previously kept uppercase letters and mapped underscores through, which the Cursor adapter (and the unified plugin target) reject — a cursor/plugin target selection would scaffold a project whose own validate fails. The sanitizer now lowers to the strictest host contract (lowercase letters, digits, dots, hyphens, 64-char cap), which is also a valid safe package-output name, so every selectable target validates. Review finding on #62.
Summary
RFC #50 Phase 3, item 1: the
create-agent-bundlescaffolder —npm create agent-bundle(once npm releases exist) /npx create-agent-bundle, shipped through the same pkg.pr.new preview channel as the other workspace packages.UX (modeled on the rstack
create-*family,rstackjs/create-rstack)@clack/prompts— the same toolkitcreate-rstackuses (and the repo already carries transitively via@changesets/cli). Bundled by Rslib as a devDependency, so the published package has zero runtime dependencies, matchingcreate-rstack's tarball shape. Flag parsing isnode:utilparseArgs— no CLI framework.--template,--targets,--package-manager,--no-install,--framework-version,-d/--dir,-h/--help. Adopted fromcreate-rstack: the positional-dir →--dirfallback,foo/bar/@scope/nameproject-name semantics (formatProjectName), package-manager detection fromnpm_config_user_agent, prompt cancellation exiting 0, and the "directory + template on the command line means scripted — ask nothing" rule. Deliberately not adopted:--override(a non-empty target directory is a hard error) and git init.agent-bundle: "workspace:*"as the placeholder (the same placeholder conventioncreate-rstack'supdatePackageJsonrewrites). The scaffolder pins it to--framework-versionverbatim, or derives the pkg.pr.new preview URL from its own-preview-<sha>version at run time — both tarballs of one commit share the sha, so scaffolder and framework always pair. A non-preview build refuses to guess, because theagent-bundlenpm name belongs to an unrelated package (see below).Templates (real checked-in dirs under
templates/, copied file-by-file)minimalskills/getting-started), skills-only configmcp-serversrc/mcp/status.tsfactory entry (framework lifecycle shell) + one artifact script + shared domain modulecli-toolsrc/cli.tsbin convention +src/index.tslib with dts; the CLI is also declared as a script — one bundle, two destinationsEvery template:
agent-bundle.config.ts, manifest, standalone tsconfig, one passing test, README with preview-channel install notes (perdocs/preview-packages.md),.gitignore, and acheckscript (validate + build + typecheck + test). Scaffolded output validates with zero diagnostics including the AB473x nudges (asserted in the e2e). Template manifests are checked in aspackage_jsonand.gitignoreasgitignore(thecreate-rstackrename-table trick) so the published tarball stays publint-clean and npm cannot strip files.Tests
workspace:*elimination, targets rewrite).test:packedpool, not the integration pool — it runsnpm pack/npm install): reuses the packed-consumer mechanism (copy package →rslib build --dist-path→npm pack→npm install --ignore-scripts <tarball>). It installs the scaffolder tarball, scaffolds each template with--framework-version file:<agent-bundle tarball>(no pkg.pr.new dependency), runs each project's owncheckend to end, and asserts: clean validate, lifecycle-shell wrap +mcp list/invokeformcp-server, and the framework-built executable bin + lib dts + artifact script forcli-tool. Theminimalleg covers the scaffolder-driven auto-install path.npm name finding
npm view create-agent-bundle→ 404, the name is unclaimed.agent-bundleremains taken by an unrelated package (0.1.2, "CLI tool and framework for defining, developing, and shipping AI agent skills"). The scaffolder ships to the preview channel regardless; the real-name decision stays deferred repo-wide.Wiring
Root
build,lint:package(publint),typecheck,preview:publish, andtest:packedcover the new package; template test files are excluded from the workspace pools (they run inside scaffolded projects). Changeset:create-agent-bundleminor — changesets versions the unreleased package from its checked-in0.0.0to0.1.0on first release.Gate results (local)
pnpm build✅ ·pnpm typecheck✅ ·pnpm lint✅ (0 errors, 0 warnings, templates included) ·pnpm lint:package✅ (publint "All good!" for both packages)pnpm test:unit✅ 1757 passed / 0 failedgit status examples/clean);examples-contract.test.tsspot run ✅ 3/3Refs #50 (Phase 3).